﻿using System.Linq.Dynamic.Core;
using Mapster;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;

namespace Devonline.AspNetCore;

/// <summary>
/// 业务数据附属集合类数据处理服务
/// 不提供一次性新增或修改所有附属集合的功能, 因为不明确附属集合类型的数量, 实际业务中需要的时候调用即可
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TElement">集合元素数据类型</typeparam>
/// <typeparam name="TKey">主键类型</typeparam>
public class DataWithCollectionService<TDbContext, TEntitySet, TElement, TKey> :
    DataService<TDbContext, TEntitySet, TKey>,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement, TKey>,
    IDataService<TDbContext, TEntitySet, TKey>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<TKey>
    where TElement : class, IEntitySet<TKey>
    where TKey : IConvertible
{
    protected readonly DbSet<TElement> _dbSetElement;
    public DataWithCollectionService(
        TDbContext context,
        ILogger<DataWithCollectionService<TDbContext, TEntitySet, TElement, TKey>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting)
    {
        _dbSetElement = _context.Set<TElement>();
    }

    #region 实现自 IDataWithCollectionService 接口的方法
    /// <summary>
    /// 新增业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public async Task AddCollectionAsync(TEntitySet entitySet, IEnumerable<TElement>? elements = null, string? foreignKey = null)
    {
        elements ??= GetCollectionMembers<TElement>(entitySet);
        if (elements == null || !elements.Any())
        {
            return;
        }

        foreignKey ??= GetForeignKey(foreignKey);
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            return;
        }

        var propertyInfo = typeof(TElement).GetProperty(foreignKey);
        if (propertyInfo == null)
        {
            return;
        }

        foreach (var element in elements)
        {
            Create(element);
            Update(element);
            Detach(element);
            propertyInfo.SetValue(element, entitySet.Id);
            await _dbSetElement.AddAsync(element);
        }
    }
    /// <summary>
    /// 更新业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public async Task UpdateCollectionAsync(TEntitySet entitySet, IEnumerable<TElement>? elements = null, string? foreignKey = null, bool isLogical = false)
    {
        elements ??= GetCollectionMembers<TElement>(entitySet);
        if (elements == null || !elements.Any())
        {
            return;
        }

        foreignKey ??= GetForeignKey(foreignKey);
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            return;
        }

        var propertyInfo = typeof(TElement).GetProperty(foreignKey);
        if (propertyInfo == null)
        {
            return;
        }

        //TODO TBD, 此处的 AdapterConfig 使用了基类的方法, 并没有办法针对类型做出具体设置, 只是一个通用基类适配方法
        var adapterConfig = GetAdapterConfig<TElement>();
        var olds = await _dbSetElement.Where($"{foreignKey} == @0", entitySet.Id).ToListAsync();
        foreach (var entity in elements)
        {
            var old = olds.FirstOrDefault(x => x.Id.Equals(entity.Id));
            if (old == null)
            {
                //新的有旧的没有, 则新增
                Create(entity);
                Update(entity);
                Detach(entity);
                propertyInfo.SetValue(entity, entitySet.Id);
                await _dbSetElement.AddAsync(entity);
            }
            else
            {
                //新的有旧的也有, 则更新
                //先对原数据进行逻辑更新
                Update(old, isLogical);
                entity.Adapt(old, adapterConfig);
                Update(old);
                _dbSetElement.Update(old);
            }
        }

        foreach (var old in olds)
        {
            if (!elements.Any(x => x.Id.Equals(old.Id)))
            {
                //旧的有新的没有, 则删除
                Delete(old, isLogical);
            }
        }
    }
    /// <summary>
    /// 删除业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public async Task DeleteCollectionAsync(TKey id, IEnumerable<TElement>? elements = null, string? foreignKey = null, bool isLogical = false)
    {
        foreignKey ??= GetForeignKey(foreignKey);
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            return;
        }

        elements ??= await _dbSetElement.Where($"{foreignKey} == @0", id).ToListAsync();
        if (elements == null || elements.Any())
        {
            return;
        }

        foreach (var element in elements)
        {
            Delete(element, isLogical);
        }
    }

    /// <summary>
    /// 新增业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <returns></returns>
    public async Task AddCollectionAsync<TModel>(TModel model, IEnumerable<TElement>? elements = null, string? foreignKey = null) where TModel : class, IEntitySet<TKey>
    {
        elements ??= GetCollectionMembers<TModel, TElement>(model);
        if (elements == null || !elements.Any())
        {
            return;
        }

        foreignKey ??= GetForeignKey<TModel>(foreignKey);
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            return;
        }

        var propertyInfo = typeof(TElement).GetProperty(foreignKey);
        if (propertyInfo == null)
        {
            return;
        }

        foreach (var element in elements)
        {
            Create(element);
            Update(element);
            Detach(element);
            propertyInfo.SetValue(element, model.Id);
            await _dbSetElement.AddAsync(element);
        }
    }
    /// <summary>
    /// 更新业务数据对象中 TElement 类型的集合对象
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="model">业务数据</param>
    /// <param name="elements">引用数据集合</param>
    /// <param name="foreignKey">外键字段名</param>
    /// <param name="isLogical">是否逻辑操作, 默认不是</param>
    /// <returns></returns>
    public async Task UpdateCollectionAsync<TModel>(TModel model, IEnumerable<TElement>? elements = null, string? foreignKey = null, bool isLogical = false) where TModel : class, IEntitySet<TKey>
    {
        elements ??= GetCollectionMembers<TModel, TElement>(model);
        if (elements == null || !elements.Any())
        {
            return;
        }

        foreignKey ??= GetForeignKey<TModel>(foreignKey);
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            return;
        }

        var propertyInfo = typeof(TElement).GetProperty(foreignKey);
        if (propertyInfo == null)
        {
            return;
        }

        //TODO TBD, 此处的 AdapterConfig 使用了基类的方法, 并没有办法针对类型做出具体设置, 只是一个通用基类适配方法
        var adapterConfig = GetAdapterConfig<TElement>();
        var olds = await _dbSetElement.Where($"{foreignKey} == @0", model.Id).ToListAsync();
        foreach (var entity in elements)
        {
            var old = olds.FirstOrDefault(x => x.Id.Equals(entity.Id));
            if (old == null)
            {
                //新的有旧的没有, 则新增
                Create(entity);
                Update(entity);
                Detach(entity);
                propertyInfo.SetValue(entity, model.Id);
                await _dbSetElement.AddAsync(entity);
            }
            else
            {
                //新的有旧的也有, 则更新
                //先对原数据进行逻辑更新
                Update(old, isLogical);
                entity.Adapt(old, adapterConfig);
                Update(old);
                _dbSetElement.Update(old);
            }
        }

        foreach (var old in olds)
        {
            if (!elements.Any(x => x.Id.Equals(old.Id)))
            {
                //旧的有新的没有, 则删除
                Delete(old, isLogical);
            }
        }
    }
    #endregion

    #region 重载自 DataService 的方法
    /// <summary>
    /// 此重载新增方法会同时将当前类型的集合成员一并新增
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="context">数据操作上下文</param>
    public override async Task<TEntitySet> AddAsync(TEntitySet entitySet, DataServiceContext? context = default)
    {
        _logger.LogInformation("The user {user} will add {typeName} and it's related collection elements, the content is: " + entitySet.ToJsonString(), UserName, _typeName);

        await ThrowIfExistAsync(entitySet.Id);
        await UniqueAsync(entitySet);
        Create(entitySet);
        Update(entitySet);
        Detach(entitySet);
        await _dbSet.AddAsync(entitySet);
        await AddCollectionAsync(entitySet, default, context?.ForeignKey ?? default);
        await SaveChangesAsync(context);
        return entitySet;
    }
    /// <summary>
    /// 重载的更新方法会同时将当前业务数据引用的数据集合一并更新
    /// </summary>
    /// <param name="entitySet">业务数据</param>
    /// <param name="context">数据操作上下文</param>
    public override async Task<TEntitySet> UpdateAsync(TEntitySet entitySet, DataServiceContext? context = default)
    {
        var isLogical = context?.IsLogical ?? false;
        _logger.LogDebug("The user {user} will {isLogical} update {typeName} and it's related collection elements, the content is: " + entitySet.ToJsonString(), UserName, GetLogicalString(isLogical), _typeName);

        await UniqueAsync(entitySet);
        var entity = await GetIfExistAsync(entitySet.Id);

        //先对原数据进行逻辑更新操作
        Update(entity, isLogical);

        //修改时先查了一次当前对象, 因此不能取消附加
        //Detach(entity);
        entitySet.Adapt(entity, context?.AdapterConfig ?? GetAdapterConfig());
        Update(entity);
        _dbSet.Update(entity);

        await UpdateCollectionAsync(entitySet, default, context?.ForeignKey ?? default, isLogical);
        await SaveChangesAsync(context);
        return entitySet;
    }
    /// <summary>
    /// 重载的删除方法会同时将当前业务数据引用的数据集合一并删除
    /// </summary>
    /// <param name="id">业务数据主键</param>
    /// <param name="context">数据操作上下文</param>
    /// <returns></returns>
    public override async Task DeleteAsync(TKey id, DataServiceContext? context = default)
    {
        var isLogical = context?.IsLogical ?? false;
        _logger.LogDebug("The user {user} will {isLogical} delete {typeName} and it's related collection elements, the content id is: " + id, UserName, GetLogicalString(isLogical), _typeName);
        await DeleteCollectionAsync(id, default, context?.ForeignKey ?? default, isLogical);
        await base.DeleteAsync(id, context);
    }

    /// <summary>
    /// 此重载新增方法会同时将当前类型的某一种集合成员一并新增
    /// </summary>
    /// <typeparam name="TModel">业务数据类型或其视图类型</typeparam>
    /// <param name="model">业务数据或其视图</param>
    /// <param name="context">数据操作上下文</param>
    /// <returns></returns>
    public override async Task<TModel> AddAsync<TModel>(TModel model, DataServiceContext? context = default)
    {
        _logger.LogInformation("The user {user} will add {typeName} and it's related collection elements, the content is: " + model.ToJsonString(), UserName, typeof(TModel).Name);
        await AddCollectionAsync(model, default, context?.ForeignKey ?? default);
        return await base.AddAsync(model, context);
    }
    /// <summary>
    /// 重载的更新方法会同时将当前业务数据引用的数据集合一并更新
    /// </summary>
    /// <typeparam name="TModel">业务数据类型或其视图类型</typeparam>
    /// <param name="model">业务数据或其视图</param>
    /// <param name="context">数据操作上下文</param>
    /// <returns></returns>
    public override async Task<TModel> UpdateAsync<TModel>(TModel model, DataServiceContext? context = default)
    {
        var isLogical = context?.IsLogical ?? false;
        _logger.LogDebug("The user {user} will {isLogical} update {typeName} and it's related collection elements, the content is: " + model.ToJsonString(), UserName, GetLogicalString(isLogical), typeof(TModel).Name);
        await UpdateCollectionAsync(model, default, context?.ForeignKey ?? default, isLogical);
        return await base.UpdateAsync(model, context);
    }
    #endregion

    #region 内部成员方法
    /// <summary>
    /// 获取可用外键名或者抛出异常
    /// </summary>
    /// <param name="foreignKey">业务类型对于 TElement 类型的外键</param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    protected virtual string GetForeignKey(string? foreignKey = default)
    {
        foreignKey ??= typeof(TElement).GetForeignKey<TEntitySet, TKey>();
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            throw new ArgumentNullException(nameof(foreignKey), "foreign key can't be null!");
        }

        return foreignKey;
    }
    /// <summary>
    /// 获取可用外键名或者抛出异常
    /// </summary>
    /// <typeparam name="TModel">业务数据类型</typeparam>
    /// <param name="foreignKey">业务类型对于 TElement 类型的外键</param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException"></exception>
    protected virtual string GetForeignKey<TModel>(string? foreignKey = default) where TModel : class, IEntitySet<TKey>
    {
        foreignKey ??= typeof(TElement).GetForeignKey<TModel, TKey>();
        if (string.IsNullOrWhiteSpace(foreignKey))
        {
            throw new ArgumentNullException(nameof(foreignKey), "foreign key can't be null!");
        }

        return foreignKey;
    }
    #endregion
}

/// <summary>
/// 字符串作为主键的业务数据附属集合类数据处理服务
/// 不提供一次性新增或修改所有附属集合的功能, 因为不明确附属集合类型的数量, 实际业务中需要的时候调用即可
/// </summary>
/// <typeparam name="TDbContext">数据库上下文</typeparam>
/// <typeparam name="TEntitySet">业务数据类型</typeparam>
/// <typeparam name="TElement">集合元素数据类型</typeparam>
public class DataWithCollectionService<TDbContext, TEntitySet, TElement> :
    DataWithCollectionService<TDbContext, TEntitySet, TElement, string>,
    IDataWithCollectionService<TDbContext, TEntitySet, TElement>,
    IDataService<TDbContext, TEntitySet>
    where TDbContext : DbContext
    where TEntitySet : class, IEntitySet<string>
    where TElement : class, IEntitySet<string>
{
    public DataWithCollectionService(
        TDbContext context,
        ILogger<DataWithCollectionService<TDbContext, TEntitySet, TElement>> logger,
        IDistributedCache cache,
        IHttpContextAccessor httpContextAccessor,
        AppSetting appSetting
        ) : base(context, logger, cache, httpContextAccessor, appSetting) { }
}